﻿
Imports std
Imports managedServer
Imports storoomc
Imports lpmsengi

Public Module idsystem
    Public Const defaultConfigfile As String = "idsystem.ini"
    Public Const urkUskSeperator As Char = character.newline
    Public ReadOnly urkUskSeperatorLength As Int32 = strlen(urkUskSeperator)
    Public ReadOnly keyLength As Int32 = 0
    Public storage As storoomTalk = Nothing
    Public cache As storoomTalk = Nothing

    Sub New()
        keyLength = strlen(generateRandomKey())
    End Sub

    Private Function generateRandomKey() As String
        Return createGUID()
    End Function

    Private Class storoomOperation
        Private Shared Function bytes_string(ByVal buff As bytes) As String
            assert(Not buff Is Nothing)
            Return encoding.utf8().GetString(+buff)
        End Function

        Private Shared Function string_bytes(ByVal s As String) As bytes
            If isemptystring(s) Then
                Return bytes.empty()
            Else
                Return New bytes(encoding.utf8().GetBytes(s))
            End If
        End Function

        Public Shared Function read(ByVal s As storoomTalk, _
                                    ByVal key As String, _
                                    ByRef output As String) As Boolean
            assert(Not s Is Nothing)
            Dim buff As bytes = Nothing
            If s.read(key, buff) Then
                output = bytes_string(buff)
                Return True
            Else
                Return False
            End If
        End Function

        Public Shared Function modify(ByVal s As storoomTalk, _
                                      ByVal key As String, _
                                      ByVal value As String) As Boolean
            assert(Not s Is Nothing)
            Return s.modify(key, string_bytes(value))
        End Function

        Public Shared Function uniqueWrite(ByVal s As storoomTalk, _
                                           ByVal key As String, _
                                           ByVal value As String) As Boolean
            assert(Not s Is Nothing)
            Return s.uniqueWrite(key, string_bytes(value))
        End Function

        Public Shared Function seek(ByVal s As storoomTalk, ByVal key As String) As Boolean
            assert(Not s Is Nothing)
            Return s.seek(key)
        End Function
    End Class

    Private Function setUserRandomKey(ByVal id As String, ByVal key As String) As Boolean
        Return storoomOperation.modify(cache, storoomkey.userRandom(id), key)
    End Function

    Public Function setUserRandomKey(ByVal context As httpContext, ByVal id As String, _
                                     ByVal forceUpdate As Boolean, _
                                     Optional ByRef urk As String = Nothing) As Boolean
        If forceUpdate OrElse Not userRandomKey(id, urk) Then
            urk = generateRandomKey()
            If Not setUserRandomKey(id, urk) Then
                raiseError(errorHandle.errorType.warning, "cannot set user random key to cache, id ", id)
                Return False
            End If
        End If
        If Not context.setforevercookie(Nothing, Nothing, cookie.id, id) Then
            raiseError(errorHandle.errorType.warning, _
                       "cannot set user cookie for ", cookie.id, ", value ", id)
            Return False
        End If
        If Not context.setforevercookie(Nothing, Nothing, cookie.userRandomKey, urk) Then
            raiseError(errorHandle.errorType.warning, "cannot set cookie to user, id ", id)
            Return False
        End If

        Return True
    End Function

    Public Function userRandomKey(ByVal id As String, ByRef key As String) As Boolean
        Return storoomOperation.read(cache, storoomkey.userRandom(id), key)
    End Function

    Public Function setUserPassword(ByVal id As String, ByVal pwd As String) As Boolean
        Return storoomOperation.uniqueWrite(storage, storoomkey.userPassword(id), pwd)
    End Function

    Public Function userPassword(ByVal id As String, ByRef pwd As String) As Boolean
        Return storoomOperation.read(storage, storoomkey.userPassword(id), pwd)
    End Function

    Public Function existUser(ByVal id As String) As Boolean
        If isemptystring(id) Then
            Return False
        Else
            Return storoomOperation.seek(storage, storoomkey.userPassword(id))
        End If
    End Function

    Private Function combineUserSiteRandomKey(ByVal urk As String, ByVal usk As String) As String
        Return urk + urkUskSeperator + usk
    End Function

    Private Function splitUserSiteRandomKey(ByVal combineKey As String, _
                                            ByRef urk As String, _
                                            ByRef usk As String) As Boolean
        If strlen(combineKey) <> (keyLength << 1) + urkUskSeperatorLength Then
            raiseError(errorHandle.errorType.exclamation, "combineKey <> urk + urkUskSeperator + usk")
            Return False
        Else
            Dim index As Int64
            index = strindexof(combineKey, urkUskSeperator)
            If index <> keyLength Then
                raiseError(errorHandle.errorType.exclamation, "urkUskSeperator is not right after urk")
                Return False
            Else
                urk = strleft(combineKey, index)
                usk = strmid(combineKey, index + strlen(urkUskSeperator))
                Return True
            End If
        End If
    End Function

    Private Function setUserSiteRandomKeyImpl(ByVal id As String, ByVal host As String, _
                                              ByVal ip As String, _
                                              ByVal userRandomKey As String, _
                                              ByVal userSiteRandomKey As String) As Boolean
        Return storoomOperation.modify(cache, storoomkey.userSiteRandom(id, host, ip), _
                                       combineUserSiteRandomKey(userRandomKey, userSiteRandomKey))
    End Function

    Private Function setUserSiteRandomKey(ByVal id As String, _
                                          ByVal host As String, _
                                          ByVal userRandomKey As String, _
                                          ByVal ips As vector(Of String), _
                                          ByRef userSiteRandomKey As String) As Boolean
        assert(Not ips Is Nothing)
        If strlen(userRandomKey) <> keyLength Then
            Return False
        End If
        userSiteRandomKey = generateRandomKey()
        For i As Int64 = 0 To ips.size() - 1
            If Not setUserSiteRandomKeyImpl(id, host, ips(i), userRandomKey, userSiteRandomKey) Then
                Return False
            End If
        Next
        Return True
    End Function

    Private Function getIpFromUrl(ByVal url As String, ByRef ips As vector(Of String), ByRef host As String) As Boolean
        If urlUtils.splitUrl(url, Nothing, Nothing, Nothing, host, _
                             Nothing, CType(Nothing, String), CType(Nothing, String), Nothing, Nothing) Then
            ips = publicIps(host)
            Return (Not ips Is Nothing)
        Else
            Return False
        End If
    End Function

    Public Function setUserSiteRandomKey(ByVal id As String, ByVal url As String, _
                                         ByVal userRandomKey As String, _
                                         ByRef host As String, _
                                         ByRef userSiteRandomKey As String) As Boolean
        Dim ips As vector(Of String) = Nothing
        If getIpFromUrl(url, ips, host) Then
            Return setUserSiteRandomKey(id, host, userRandomKey, ips, userSiteRandomKey)
        Else
            raiseError(errorHandle.errorType.warning, "cannot get ips for url ", url, ", host ", host)
            Return False
        End If
    End Function

    Public Function userSiteRandomKey(ByVal id As String, _
                                      ByVal host As String, _
                                      ByVal ip As String, _
                                      ByRef key As String) As Boolean
        Dim s As String = Nothing
        If storoomOperation.read(cache, storoomkey.userSiteRandom(id, host, ip), s) Then
            Dim urk As String = Nothing
            Dim usk As String = Nothing
            If splitUserSiteRandomKey(s, urk, usk) AndAlso userRandomKey(id, s) AndAlso strsame(s, urk) Then
                key = usk
                Return True
            Else
                Return False
            End If
        Else
            Return False
        End If
    End Function

    Public Function ipVerify(ByVal ip As String) As Boolean
        Return Net.IPAddress.TryParse(ip, Nothing)
    End Function

    Public Function idVarify(ByVal id As String) As Boolean
        Return strlen(id) >= minIdLength
    End Function

    Public Function getContextServer(ByVal context As httpContext) As String
        assert(Not context Is Nothing)
        Return context.url().protocol + network.protocolMark + network.pathSeperator + network.pathSeperator + _
               context.url().host + network.portMark + context.url().port
    End Function

    Public Function processInputSpamming(ByVal context As httpContext) As Boolean
        Return context.createControl(New nameLpmsControl("inputspamming")).render()
    End Function

    Public Sub main(ByVal args() As String)
        ProHttpS.insertRequestProcessor(New configDelegate())
        ProHttpS.insertRequestProcessor(New login())
        ProHttpS.insertRequestProcessor(New apilogin())
        ProHttpS.insertRequestProcessor(New usrk())
        ProHttpS.insertRequestProcessor(New register())
        If isdebugmode() Then
            ProHttpS.insertRequestProcessor(New inputspamming())
        End If
        ProHttpS.insertRequestProcessor(New defaultlogin())
        ProHttpS.insertRequestProcessor(New nameCacheProcessor("homepage", _
                                                               network.pathSeperator, _
                                                               secondsToTicks(30 * 60), _
                                                               cacheProcessor.cachetypeEnum.none))
        ProHttpS.defaultConfigfile = defaultConfigfile
        ProHttpS.startServer(args, configNaming.idsystem.section)
    End Sub
End Module

Namespace configNaming
    Namespace idsystem
        Public Module _idsystem
            Public Const section As String = "idsystem"
        End Module
    End Namespace

    Namespace storage
        Public Module _storage
            Public Const section As String = "storage"
        End Module
    End Namespace

    Namespace cache
        Public Module _cache
            Public Const section As String = "cache"
        End Module
    End Namespace
End Namespace
